/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						 can_cast.cpp

	$Header: /heroes4/can_cast.cpp $

	$NoKeywords: $

 ************************************************************************/
// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "can_cast.h"

#include "battlefield.h"
#include "combat_creature.h"
#include "creature_stack.h"
#include "creature_type.h"
#include "enum_operations.h"
#include "external_string.h"
#include "format_string.h"
#include "keyword_replacer.h"
#include "replace_keywords.h"
#include "spell.h"
#include "spell_properties.h"
#include "town_properties.h"
#include "town_type.h"

// -----------------------------------------------------------------------------
// set help text
// ------------------------------------------------------------------------------
inline void set_help( std::string* help_text, std::string const& text )
{
	if (help_text != 0)
		*help_text = text;
}

// -----------------------------------------------------------------------------
// set help text based on number
// ------------------------------------------------------------------------------
inline void set_help( std::string* help_text, t_abstract_combat_creature const& creature,
					  std::string const& singular, std::string const& plural )
{
	if (help_text != 0)
	{
		if (creature.get_number() == 1)
			*help_text = singular;
		else
			*help_text = plural;
	}
}

// -----------------------------------------------------------------------------
// set help text based on number
// ------------------------------------------------------------------------------
static t_external_string const k_text_spell_protects( "spell_protects.spell_name" );

inline void protective_spell( std::string* help_text, t_spell spell, 
							  std::string const& text = k_text_spell_protects )
{
	if (help_text != 0)
	{
		*help_text = text;
		*help_text = replace_keywords( *help_text, "%Protective_spell", get_spell_name( spell ));
	}
}


// -----------------------------------------------------------------------------
// get chance of a spell affecting a creature
// ------------------------------------------------------------------------------
static t_external_string const k_text_is_immune_to_magic( "immune_to_magic.is.spells" );
static t_external_string const k_text_are_immune_to_magic( "immune_to_magic.are.spells" );
static t_external_string const k_text_is_immune_to_fire( "immune_to_fire.is.spells" );
static t_external_string const k_text_are_immune_to_fire( "immune_to_fire.are.spells" );
static t_external_string const k_text_is_immune_to_cold( "immune_to_cold.is.spells" );
static t_external_string const k_text_are_immune_to_cold( "immune_to_cold.are.spells" );
static t_external_string const k_text_is_immune_to_harmful( "immune_to_harm.is.spells" );
static t_external_string const k_text_are_immune_to_harmful( "immune_to_harm.are.spells" );
static t_external_string const k_text_is_immune_to_spell( "immune_to_spell.is.spells" );
static t_external_string const k_text_are_immune_to_spell( "immune_to_spell.are.spells" );

static bool check_immunity( t_spell spell, t_abstract_combat_creature const& target, 
						    std::string* help_text )
{
	if (target.has_ability( k_ability_spell_vulnerability ))
		return true;
	if (target.has_ability( k_ability_magic_immunity ))
	{
		set_help( help_text, target, k_text_is_immune_to_magic, k_text_are_immune_to_magic );
		return false;
	}
	if (target.is_active( k_spell_sanctuary ))
	{
		protective_spell( help_text, k_spell_sanctuary );
		return false;
	}
	if (target.is_active( k_spell_anti_magic ))
	{
		protective_spell( help_text, k_spell_anti_magic );
		return false;
	}
	if (is_fire( spell ))
	{
		if (target.is_active( k_spell_fire_resistance ))
		{
			protective_spell( help_text, k_spell_fire_resistance );
			return false;
		}
		if (target.has_ability( k_ability_fire_resistance ))
		{
			set_help( help_text, target, k_text_is_immune_to_fire, k_text_are_immune_to_fire );
			return false;
		}
	}
	if (is_cold( spell ))
	{
		if (target.is_active( k_spell_cold_resistance ))
		{
			protective_spell( help_text, k_spell_cold_resistance );
			return false;
		}
		if (target.has_ability( k_ability_cold_resistance ))
		{
			set_help( help_text, target, k_text_is_immune_to_cold, k_text_are_immune_to_cold );
			return false;
		}
	}
	if (is_fear( spell ) && target.has_ability( k_ability_berserk ))
	{
		set_help( help_text, target, k_text_is_immune_to_spell, k_text_are_immune_to_spell );
		return false;
	}
	return true;
}

// -----------------------------------------------------------------------------
// get chance of a spell affecting a creature
// ------------------------------------------------------------------------------
static t_external_string const k_text_not_mechanical( "not_mechanical.spells" );
static t_external_string const k_text_not_undead( "not_undead.spells" );
static t_external_string const k_text_not_elemental( "not_elemental.spells" );
static t_external_string const k_text_no_effect( "no_effect.spells" );
static t_external_string const k_text_not_illusion( "not_illusion.misc" );

static bool check_creature_type( t_spell				  spell,
								 t_abstract_combat_creature const* caster,
			                     t_abstract_combat_creature const& target,
								 std::string*			  help_text )
{
	if (target.has_ability( k_ability_spell_vulnerability ))
		return true;
	// the Crown of Enchantment shortcuts this test for mind spells.
	if (caster != 0 && is_mind( spell ) 
		&& caster->has_ability( k_ability_negate_mind_immunity ))
		return true;

	if (!affects_mechanical( spell ) && target.has_ability( k_ability_mechanical ))
	{
		if (target.is_active(k_spell_illusionary_ally) || target.is_active( k_spell_clone))
			set_help( help_text, k_text_not_illusion );
		else
			set_help( help_text, k_text_not_mechanical );
		return false;
	}
	if (!affects_undead( spell ) && target.has_ability( k_ability_undead ))
	{
		set_help( help_text, k_text_not_undead );
		return false;
	}
	if (!affects_elementals( spell) && target.has_ability( k_ability_elemental ))
	{
		set_help( help_text, k_text_not_elemental );
		return false;
	}
	return true;
}

// -----------------------------------------------------------------------------
// get chance of a spell affecting a creature
// ------------------------------------------------------------------------------
static t_external_string const k_text_protection_from_type( "protected.spells" );
static t_external_string const k_text_is_immune_to_type( "immune_to_type.is.spells" );
static t_external_string const k_text_are_immune_to_type( "immune_to_type.are.spells" );
static t_external_string const k_text_artifact_protection( "artifact_protection.misc" );

static bool check_protection( t_spell spell, t_abstract_combat_creature const* caster,
							  t_abstract_combat_creature const& target,
							 std::string* help_text )
{
	t_town_type alignment = get_spell_alignment( spell );

	if (target.has_ability( k_ability_spell_vulnerability ))
		return true;
	// check artifact protection
	if (target.has_artifact_immunity( spell, help_text ))
	{
		if (help_text != 0)
			*help_text = replace_keywords( k_text_artifact_protection, "%an_artifact",
										   *help_text );
		return false;
	}
	return true;
}

// ------------------------------------------------------------------------------
// check Song of Peace
// ------------------------------------------------------------------------------
static t_external_string const k_text_is_peaceful( "peaceful.is.spells" );
static t_external_string const k_text_are_peaceful( "peaceful.are.spells" );

bool check_song_of_peace( t_combat_creature const& caster, t_spell spell, 
						  std::string* help_text )
{
	if (!caster.is_active( k_spell_song_of_peace ))
		return true;
	if (!is_curse( spell ) && !is_damage( spell ))
		return true;
	set_help( help_text, caster, k_text_is_peaceful, k_text_are_peaceful );
	if (help_text != 0)
		*help_text = replace_spell_keywords( *help_text, caster, spell, 0 );
	return false;
}


// -----------------------------------------------------------------------------
// get chance of a spell affecting a creature
// ------------------------------------------------------------------------------
static t_external_string const k_text_not_curse( "blessing.spells" );
static t_external_string const k_text_not_blessing( "curse.spells" );

static bool check_ownership( t_abstract_combat_creature const& caster, t_spell spell, 
							t_abstract_combat_creature const& target, std::string* help_text )
{
	if (!affects_enemies( spell ) && caster.is_hostile( target ))
	{
		set_help( help_text, k_text_not_curse );
		return false;
	}
	if (!affects_friends( spell ) && caster.is_friendly( target ))
	{
		set_help( help_text, k_text_not_blessing );
		return false;
	}
	if (!caster.is_friendly( target ) && caster.is_active( k_spell_song_of_peace )
		&& (is_curse( spell ) || is_damage( spell ) || is_summoning( spell )))
	{
		set_help( help_text, caster, k_text_is_peaceful, k_text_are_peaceful );
		return false;
	}
	return true;
}

// -----------------------------------------------------------------------------
// get chance of a spell affecting a creature
// ------------------------------------------------------------------------------
static t_external_string const k_text_has_no_blessings( "no_blessings.has.spells" );
static t_external_string const k_text_have_no_blessings( "no_blessings.have.spells" );

inline bool check_cancellation( t_abstract_combat_creature const& target, std::string* help_text )
{
	t_spell target_spell;

	for (target_spell = t_spell(0); target_spell < k_spell_count;
		 enum_incr(target_spell))
		if (target.is_active( target_spell ) && !is_physical( target_spell ) 
			&& target.can_remove( target_spell )
			&& spell_is( target_spell, k_spell_bit_blessing | k_spell_bit_summoning ))
			return true;
	set_help( help_text, target, k_text_has_no_blessings, k_text_have_no_blessings );
	return false;
}

// -----------------------------------------------------------------------------
// get chance of a spell affecting a creature
// ------------------------------------------------------------------------------
static t_external_string const k_text_has_no_curses( "no_curses.has.spells" );
static t_external_string const k_text_have_no_curses( "no_curses.have.spells" );

inline bool check_exorcism( t_abstract_combat_creature const& target, std::string* help_text )
{
	t_spell target_spell;

	for (target_spell = t_spell(0); target_spell < k_spell_count; enum_incr(target_spell))
		if (target.is_active( target_spell )     && !is_physical( target_spell ) 
			&& target.can_remove( target_spell ) && is_curse( target_spell ))
			return true;
	set_help( help_text, target, k_text_has_no_curses, k_text_have_no_curses );
	return false;
}

// -----------------------------------------------------------------------------
// get chance of a spell affecting a creature
// ------------------------------------------------------------------------------
static t_external_string const k_text_has_no_spells( "no_spells.has.spells" );
static t_external_string const k_text_have_no_spells( "no_spells.have.spells" );

inline bool check_dispel( t_abstract_combat_creature const& target, std::string* help_text )
{
	t_spell target_spell;

	for (target_spell = t_spell(0); target_spell < k_spell_count; enum_incr(target_spell))
		if (target.is_active( target_spell ) && !is_physical( target_spell )
			&& target.can_remove( target_spell ))
			return true;
	set_help( help_text, target, k_text_has_no_spells, k_text_have_no_spells );
	return false;
}


// -----------------------------------------------------------------------
// handle the resurrection spell
// test creature stack for dead bodies.
// -----------------------------------------------------------------------
static t_external_string const k_text_permanently_dead( "permanently_dead.spells" );
static t_external_string const k_text_none_are_dead( "not_dead.are.spells" );
static t_external_string const k_text_is_not_dead( "not_dead.is.spells" );
static t_external_string const k_text_no_room( "no_room.spells" );

inline bool check_bodies( t_abstract_combat_creature const& target, std::string* help_text ) 
{
	if (target.get_salvageable_bodies() == 0)
	{
		if (target.get_permanent_deaths() > 0)
			set_help( help_text, k_text_permanently_dead );
		else
			set_help( help_text, target, k_text_is_not_dead, k_text_none_are_dead );
		return false;
	}
	return true;
}

// -----------------------------------------------------------------------
// handle the resurrection spell
// -----------------------------------------------------------------------
inline bool check_resurrection( t_battlefield& battlefield, t_combat_creature const& target, 
							    std::string* help_text )
{
	if (!check_bodies( target, help_text ))
		return false;

	if (target.get_number() > 0)
		return true;

	if (!battlefield.can_place( target, target.get_cell_position()))
	{
		set_help( help_text,  k_text_no_room );
		return false;
	}
	return true;
}

// -----------------------------------------------------------------------------
// get chance of a spell affecting a creature
// ------------------------------------------------------------------------------
inline bool check_divine_intervention( t_abstract_combat_creature const& target )
{
	t_spell spell;

	if (target.get_number() == 0)
		return target.get_hero() != 0;
	if (target.get_wounds() == 0)
	{

		for (spell = t_spell(0); spell < k_spell_count; enum_incr(spell))
			if (target.is_active( spell ) && is_curse( spell ))
				return false;
	}
	return true;
}

// -----------------------------------------------------------------------------
// get chance of a spell affecting a creature
// ------------------------------------------------------------------------------
// ---------------------------------------------------------------
// summoning phantasm
// create a creature which is destroyed by any damage at all
// ---------------------------------------------------------------
static t_external_string const k_text_too_strong( "too_strong.spells" );
static t_external_string const k_text_create_illusion( "create_illusion.spells" );
static t_external_string const k_text_no_heroes( "cant_affect_heroes.misc" );

bool check_illusion( t_abstract_combat_creature const& caster, t_spell spell,
					 t_abstract_combat_creature const& target, std::string* help_text )
{
	int power = caster.get_spell_power( spell );
	int hit_points = target.get_hit_points();
	int number = (power + hit_points - 1) / hit_points;

	if (target.get_hero() != 0)
	{
		set_help( help_text, k_text_no_heroes );
		return false;
	}
	if (power < hit_points / 2)
	{
		set_help( help_text, k_text_too_strong );
		return false;
	}
	if (help_text != 0)
	{
		if (target.get_hero() == 0)
			*help_text = format_string( "%i ", number ) + target.get_name( false, number );
		else
			*help_text = target.get_name();
		*help_text = replace_keywords( k_text_create_illusion, "%creatures", *help_text );
	}
	return true;
}

// ---------------------------------------------------------------
// check effects for holy water
// ---------------------------------------------------------------
static t_external_string const k_text_evil_only( "evil_only.spells" );
static t_external_string const k_text_drink_or_throw( "drink.spells" );
static t_external_string const k_text_already_has_spell( "exists.spells" );
static t_external_string const k_text_already_has_ability( "already_has_ability.misc" );
static t_external_string const k_text_already_have_ability( "already_have_ability.misc" );

static bool check_holy_water( t_abstract_combat_creature const* caster, 
							  t_abstract_combat_creature const& target, 
							  std::string*			   help_text )
{
	if (caster == 0)
		return true;
	if (caster->is_hostile( target ))
	{
		if (target.get_alignment() != k_town_death 
			&& !target.has_ability( k_ability_undead ))
		{
			set_help( help_text, k_text_evil_only );
			return false;
		}
		return true;
	}
	if (caster != &target)
	{
		set_help( help_text, k_text_drink_or_throw );
		return false;
	}
	if (caster->is_active( k_spell_bless ) 
		&& caster->is_active( k_spell_protection_from_death ))
	{
		set_help( help_text, k_text_already_has_spell);
		return false;
	}
	return true;
}

// -----------------------------------------------------------------------------
// get chance of a spell affecting a creature
// ------------------------------------------------------------------------------
static bool check_drain_life( t_abstract_combat_creature const* caster, 
							  t_abstract_combat_creature const& target )
{
	t_town_type alignment;

	alignment = target.get_alignment();
	if (alignment != k_town_life && alignment != k_town_nature)
		return false;
	if (caster == &target)
		return false;
	return true;
}

// ------------------------------------------------------------------------------
// check if Steal Enchantment is legal
// ------------------------------------------------------------------------------
bool check_steal_enchantment( t_combat_creature const& caster, 
							  t_spell spell )
{
	if (!is_blessing( spell ) || is_physical( spell ))
		return false;

	t_battlefield& battlefield = caster.get_battlefield();

	t_combat_creature_list::iterator index = battlefield.creatures_begin();
	t_combat_creature_list::iterator end = battlefield.creatures_end();

	for (; index != end; index++)
	{
		if (index->get()->get_number() == 0)
			continue;
		if (index->get()->get_controller() != caster.get_controller())
			continue;
		if (can_affect( spell, **index ))
			return true;
	}
	return false;
}

// ------------------------------------------------------------------------------
// check if Steal Enchantment is legal
// ------------------------------------------------------------------------------
static t_external_string const k_text_has_no_spells_to_steal( "cannot_steal.has.spells" );
static t_external_string const k_text_have_no_spells_to_steal( "cannot_steal.have.spells" );

static bool check_steal_enchantment( t_combat_creature const& caster,
							  t_combat_creature const& target, std::string* help_text )
{
	t_spell_list const&			 list = target.get_active_spells();
	t_spell_list::const_iterator spell = list.begin();
	t_spell_list::const_iterator end = list.end();

	for (; spell != end; spell++)
	{
		assert( target.is_active( *spell ));
		if (!target.can_remove( *spell ))
			continue;
		if (check_steal_enchantment( caster, *spell ))
			return true;
	}
	set_help( help_text, target, k_text_has_no_spells_to_steal,
		      k_text_have_no_spells_to_steal );
	return false;
}


// -----------------------------------------------------------------------------
// get chance of a spell affecting a creature
// ------------------------------------------------------------------------------
static t_external_string const k_text_only_on_summoned( "only_summoned.spells" );
static t_external_string const k_text_are_not_wounded( "not_wounded.are.spells" );
static t_external_string const k_text_is_not_wounded( "not_wounded.is.spells" );
static t_external_string const k_text_banish_too_weak( "banish_too_weak.misc" );
static t_external_string const k_text_not_summoned( "not_summoned.misc" );
static t_external_string const k_text_not_spellcaster( "not_spellcaster.misc" );
static t_external_string const k_text_flyers_only( "flyers_only.spells" );
static t_external_string const k_text_has_no_spell_points( "no_spell_points.has.spells" );
static t_external_string const k_text_have_no_spell_points( "no_spell_points.have.spells" );

static bool check_spell( t_abstract_combat_creature const* caster, t_spell spell, 
						 t_abstract_combat_creature const& target, std::string* help_text )
{
	switch (spell)
	{
		case k_spell_animate_dead:
		case k_spell_death_call:
		case k_spell_raise_skeletons:
		case k_spell_raise_ghost:
		case k_spell_raise_vampires:
			if (target.is_active( k_spell_sacred_ground )
				&& !target.has_ability( k_ability_spell_vulnerability ))
			{
				protective_spell( help_text, k_spell_sacred_ground );
				return false;
			}
			if (target.get_hero() != 0)
			{
				set_help( help_text, k_text_no_heroes );
				return false;
			}
			return check_bodies( target, help_text );

		case k_spell_armageddon:
			if (&target == caster)
				return false;
			return true;

		case k_spell_banish:
			if (!target.is_summoned())
			{
				set_help( help_text, k_text_only_on_summoned );
				return false;
			}
			if (caster != 0 && target.get_hit_points() 
							 > target.modify_spell_damage( caster, 
														   caster->get_spell_power( spell ), spell ))
			{
				set_help( help_text, k_text_banish_too_weak );
				return false;
			}
			break;

		case k_spell_bless:
		case k_spell_curse:
		case k_spell_mass_bless:
		case k_spell_mass_curse:
			if (target.get_creature_stack()->get_damage_low()
				== target.get_creature_stack()->get_damage_high())
			{
				set_help( help_text, k_text_no_effect );
				return false;
			}
			break;

		case k_spell_bind_flyer:
			if (!target.has_ability( k_ability_flying ))
			{
				set_help( help_text, k_text_flyers_only );
				return false;
			}
			break;

		case k_spell_bind_wound:
			if (target.get_wounds() == 0)
			{
				set_help( help_text, target, k_text_is_not_wounded, k_text_are_not_wounded );
				return false;
			}
			break;

		case k_spell_blind:
		case k_spell_stone_gaze:
			if (target.has_ability( k_ability_blind ) 
				&& !target.has_ability( k_ability_spell_vulnerability ))
			{
				set_help( help_text, target, k_text_is_immune_to_spell, k_text_are_immune_to_spell );
				return false;
			}
			break;

		case k_spell_cancellation:
		case k_spell_mass_cancellation:
			return check_cancellation( target, help_text );

		case k_spell_dispel:
		case k_spell_mass_dispel:
			return check_dispel( target, help_text );

		case k_spell_displacement:
			if (target.is_active( k_spell_binding ))
			{
				protective_spell( help_text, k_spell_binding );
				return false;
			}
			break;

		case k_spell_divine_intervention:
			return check_divine_intervention( target );

		case k_spell_divine_retribution:
		case k_spell_holy_word:
			if (target.get_hero() != 0)
			{
				set_help( help_text, k_text_no_heroes );
				return false;
			}
			if (target.get_alignment() != k_town_death 
				&& !target.has_ability( k_ability_undead ))
			{
				set_help( help_text, k_text_evil_only );
				return false;
			}
			break;

		case k_spell_evil_hour:
		case k_spell_prayer:
			return target.get_alignment() == k_town_life;

		case k_spell_exorcism:
		case k_spell_mass_exorcism:
			return check_exorcism( target, help_text );

		case k_spell_fear:
			if (target.has_ability( k_ability_panic )
				|| target.has_ability( k_ability_no_retaliation ))
			{
				set_help( help_text, target, k_text_already_has_ability,
						  k_text_already_have_ability );
				return false;
			}
			break;

		case k_spell_flight:
			if (target.has_ability( k_ability_flying ))
			{
				set_help( help_text, target, k_text_already_has_ability, 
					      k_text_already_have_ability );
				return false;
			}
			break;

		case k_spell_forgetfulness:
		case k_spell_greater_forgetfulness:
		case k_spell_precision:
		case k_spell_mass_precision:
			if (!target.has_ability( k_ability_ranged ))
			{
				set_help( help_text, k_text_no_effect );
				return false;
			}
			break;

		case k_spell_frozen:
		{
			t_creature_type creature_type = target.get_creature_type();

			if (creature_type == k_fire_elemental || creature_type == k_phoenix)
				return false;
			break;
		}

		case k_spell_heal:
		case k_spell_mass_healing:
			if (target.get_wounds() == 0 && !target.is_active( k_spell_poison )
				&& !target.is_active( k_spell_stun )
				&& !target.is_active( k_spell_plague ))
			{
				set_help( help_text, target, k_text_is_not_wounded, k_text_are_not_wounded );
				return false;
			}
			break;

		case k_spell_holy_water:
			return check_holy_water( caster, target, help_text );
			
		case k_spell_illusionary_ally:
		case k_spell_clone:
			if (caster == 0)
				return true;
			return check_illusion( *caster, spell, target, help_text );

		case k_spell_life_drain:
			return check_drain_life( caster, target );

		case k_spell_morale_boost:
			if (target.is_active( k_spell_spiritual_fervor ))
			{
				set_help( help_text, k_text_already_has_spell );
				return false;
			}
			return true;

		case k_spell_mana_flare:
			return (target.get_maximum_spell_points() > 0);

		case k_spell_mana_leech:
		case k_spell_power_drain:
		case k_spell_spell_shackle:
			if (target.get_maximum_spell_points() == 0)
			{
				set_help( help_text, k_text_not_spellcaster );
				return false;
			}
			if (target.get_spell_points() == 0)
			{
				set_help( help_text, target, k_text_has_no_spell_points, 
						  k_text_have_no_spell_points );
				return false;
			}
			return true;

		case k_spell_plague:
			return (target.get_alignment() != k_town_death);

		case k_spell_protection_from_chaos:
			if (target.has_ability( k_ability_chaos_protection ))
			{
				set_help( help_text, target, k_text_already_has_ability, 
					      k_text_already_have_ability );
				return false;
			}
			break;

		case k_spell_protection_from_death:
			if (target.has_ability( k_ability_death_protection ))
			{
				set_help( help_text, target, k_text_already_has_ability, 
					      k_text_already_have_ability );
				return false;
			}
			break;

		case k_spell_protection_from_life:
			if (target.has_ability( k_ability_life_protection ))
			{
				set_help( help_text, target, k_text_already_has_ability, 
					      k_text_already_have_ability );
				return false;
			}
			break;

		case k_spell_sacrifice:
			if (target.get_hero() != 0)
			{
				set_help( help_text, k_text_no_heroes );
				return false;
			}
			if (target.is_summoned())
			{
				set_help( help_text, k_text_not_summoned );
				return false;
			}
			return true;


		case k_spell_vampiric_touch:
			if (target.has_ability( k_ability_vampire ))
			{
				set_help( help_text, target, k_text_already_has_ability, 
					      k_text_already_have_ability );
				return false;
			}
			return true;
	}
	return true;
}

static bool check_specific_spell( t_battlefield& battlefield, 
								  t_combat_creature const* caster, t_spell spell, 
								  t_combat_creature const& target, std::string* help_text )
{
	switch (spell)
	{
		case k_spell_divine_intervention:
			if (target.get_number() == 0)
				return check_resurrection( battlefield, target, help_text );
			break;

		case k_spell_effect_sacrifice_client:
		case k_spell_rebirth:
		case k_spell_resurrection:
			return check_resurrection( battlefield, target, help_text );

		case k_spell_steal_enchantment:
		case k_spell_steal_all_enchantments:
			if (caster != 0)
				return check_steal_enchantment( *caster, target, help_text );
			break;
	}
	return true;
}


// ------------------------------------------------------------------------------
// return whether this kind of spell can normally target this creature
// ------------------------------------------------------------------------------
static bool check_resistance( t_abstract_combat_creature const* caster, t_spell spell,
							  t_abstract_combat_creature const& target, std::string* help_text )
{
	if (caster != 0 && !caster->is_hostile( target ))
		return true;
	if (target.has_ability( k_ability_spell_vulnerability ))
		return true;
	if (can_resist( spell ) || is_damage( spell ))
	{
		if (target.get_magic_resistance( spell ) >= 100)
		{
			set_help( help_text, target, k_text_is_immune_to_harmful,
				      k_text_are_immune_to_harmful );
			return false;
		}
	}
	return true;
}

// ------------------------------------------------------------------------------
// check if spell is already on the creature
// ------------------------------------------------------------------------------

static bool check_existing( t_abstract_combat_creature const* caster, t_spell spell,
						   t_abstract_combat_creature const& target, std::string* help_text )
{
	if (spell >= k_spell_count)
		return true;
	
	t_spell actual_spell = translate_spell( spell );

	if (target.is_active( actual_spell ))
	{
		if (actual_spell == k_spell_acid && target.get_number() == 0)
			return true;
		if (caster != 0
			&& caster->get_spell_power( actual_spell ) > target.get_effect_power( spell ))
			return true;
		set_help( help_text, k_text_already_has_spell );
		return false;
	}
	return true;
}

// -----------------------------------------------------------------------------
// can affect target
// ------------------------------------------------------------------------------
static bool can_affect_target( t_abstract_combat_creature const* caster, t_spell spell, 
				               t_abstract_combat_creature const& target, std::string* help_text )
{
	bool result;

	result = check_immunity( spell, target, help_text );
	result = result && check_creature_type( spell, caster, target, help_text );
	result = result && check_protection( spell, caster, target, help_text );
	result = result && check_spell( caster, spell, target, help_text );
	result = result && check_resistance( caster, spell, target, help_text );
	result = result && check_existing( caster, spell, target, help_text );
	if (caster != 0)
	{
		result = result && check_ownership( *caster, spell, target, help_text );
	}
	return result;
}

// -----------------------------------------------------------------------------
// can affect target
// ------------------------------------------------------------------------------
static bool can_affect( t_combat_creature const* caster, t_spell spell, 
				        t_combat_creature const& target, std::string* help_text )
{
	bool			result;
	t_battlefield&	battlefield = target.get_battlefield();

	if (target.get_number() == 0 && !targets_bodies( spell ))
	{
		set_help( help_text, "" );
		return false;
	}

	// pre-emptive check for dispel against certain spells
	if (spell == k_spell_dispel || spell == k_spell_cancellation)
	{
		if (target.is_active( k_spell_animate_dead )
			|| target.is_active( k_spell_death_call )
			|| target.is_active( k_spell_illusionary_ally )
			|| target.is_active( k_spell_clone ))
			return true;
	}
	result = can_affect_target( caster, spell, target, help_text );
	result = result && check_specific_spell( battlefield, caster, spell, target, help_text );
	return result;
}


// -----------------------------------------------------------------------------
// can affect target
// ------------------------------------------------------------------------------
bool can_affect_target( t_abstract_combat_creature const& caster, t_spell spell, 
						t_abstract_combat_creature const& target )
{
	return can_affect_target( &caster, spell, target, 0 );
}

// -----------------------------------------------------------------------------
// can affect target
// ------------------------------------------------------------------------------
bool can_affect( t_spell spell, t_combat_creature const& target )
{
	return can_affect( 0, spell, target, 0 );
}

// -----------------------------------------------------------------------------
// can affect target
// ------------------------------------------------------------------------------
bool can_affect( t_combat_creature const& caster, t_spell spell, 
				 t_combat_creature const& target  )
{
	return can_affect( &caster, spell, target, 0 );
}


// ------------------------------------------------------------------------------
// return whether this kind of spell can normally target this creature
// ------------------------------------------------------------------------------
static t_external_string const k_text_is_cowardly( "cowardly.is.spells" );
static t_external_string const k_text_are_cowardly( "cowardly.are.spells" );

static bool check_target_ownership( t_combat_creature const& caster, t_spell spell,
							        t_combat_creature const& target, std::string* help_text )
{
	// note: hypnotized creatures are neither friendly nor hostile
	// only curses and damage spells can be cast on hostile creatures
	if (!is_curse( spell ) && !is_damage( spell )
		&& !is_summoning( spell ) && caster.is_hostile( target ))
	{
		set_help( help_text, k_text_not_curse );
		return false;
	}
	// only blessings can be cast on friendly creatures
	if (!is_blessing( spell ) && !is_summoning( spell ) 
		 && caster.is_friendly( target ))
	{
		set_help( help_text, k_text_not_blessing );
		return false;
	}
	if (!caster.is_friendly( target ) && caster.check_cowardice( target ))
	{
		set_help( help_text, caster, k_text_is_cowardly, k_text_are_cowardly );
		return false;
	}
	return true;
}

// -----------------------------------------------------------------------------
// replace spell keywords
// ------------------------------------------------------------------------------
std::string replace_spell_keywords( std::string const& text, 
								    t_combat_creature const& caster, 
								    t_spell spell, t_combat_creature const* target )
{
	t_keyword_replacer replacer;
	std::string        caster_name = caster.get_name( true );
	t_town_type		   alignment = get_spell_alignment( spell );

	replacer.add_keyword( "%the_caster", caster_name );
	replacer.add_keyword( "%the_casters", caster_name );
	replacer.add_keyword( "%spell_name", get_spell_name( spell ) );
	if (target != 0)
	{
		std::string        target_name;

		target_name = target->get_name( true );
		replacer.add_keyword( "%the_creatures", target_name );
		replacer.add_keyword( "%the_creature", target_name );
		replacer.add_keyword( "%creatures", target->get_name());
	}
	replacer.add_keyword( "%magic_type", get_alignment_name( alignment ) );
	return replacer( text );
}


// -----------------------------------------------------------------------------
// check LOS
// ------------------------------------------------------------------------------
extern t_external_string const k_text_cannot_see( "cannot_see.spells" );

static bool can_see( t_combat_creature const&	caster, t_spell spell,
					 t_combat_creature const&	target, std::string* help_text )
{
	if (!is_line_of_sight( spell ))
		return true;

	t_battlefield& battlefield = caster.get_battlefield();

	if (!battlefield.can_see( caster, target ))
	{
		set_help( help_text, k_text_cannot_see );
		return false;
	}
	return true;
}

bool check_line_of_sight( t_combat_creature const& caster, t_spell spell,
						  t_combat_creature const& target, std::string* help_text )
{
	if (can_see( caster, spell, target, help_text ))
		return true;
	
	if (help_text != 0)
		*help_text = replace_spell_keywords( *help_text, caster, spell, &target );
	return false;
}

// -----------------------------------------------------------------------------
// check LOS
// ------------------------------------------------------------------------------
extern t_external_string const k_text_cannot_see_cell( "cannot_see_cell.spells" );

bool check_line_of_sight( t_combat_creature const& caster, 
						  t_spell spell,
						  t_map_point_2d const& point, std::string* help_text )
{
	t_battlefield& battlefield = caster.get_battlefield();

	if (battlefield.can_see_cell( caster, point ))
		return true;
	set_help( help_text, k_text_cannot_see_cell );
	if (help_text != 0)
		*help_text = replace_spell_keywords( *help_text, caster, spell, 0 );
	return false;
}

// -----------------------------------------------------------------------------
// check area spell for enemy targets
// ------------------------------------------------------------------------------
extern t_external_string const k_text_no_enemies( "no_enemies.spells" );
extern t_external_string const k_text_cast_spell( "cast_spell.spells" );

bool contains_enemies( t_combat_creature const& caster, t_spell spell,
					   t_combat_creature_list const& targets, std::string* help_text )
{
	t_combat_creature_list::const_iterator index = targets.begin();
	t_combat_creature_list::const_iterator end = targets.end();
	bool								   result = false;

	set_help( help_text, k_text_no_enemies );
	for (; index != end; index++)
	{
		if (!caster.is_friendly( **index ))
		{
			result = true;
			set_help( help_text, k_text_cast_spell );
			break;
		}
	}
	if (help_text != 0)
		*help_text = replace_spell_keywords( *help_text, caster, spell, 0 );
	return result;
}


// -----------------------------------------------------------------------------
// get chance of a spell affecting a creature
// ------------------------------------------------------------------------------
static t_external_string const k_text_cast_spell_on( "cast_on.spells" );

bool can_cast( t_combat_creature const& caster, t_spell spell, 
			   t_combat_creature const& target, std::string* help_text )
{
	bool result;

	if (help_text != 0)
		*help_text = k_text_cast_spell_on;
	result = can_affect( &caster, spell, target, help_text );
	result = result && check_target_ownership( caster, spell, target, help_text );
	result = result && can_see( caster, spell, target, help_text );
	if (help_text != 0)
		*help_text = replace_spell_keywords( *help_text, caster, spell, &target );
	return result;
}

